/*
 * @Author: zzr
 * @Date: 2024-12-02 16:38:51
 * @LastEditors: zzr
 * @Description:
 */
/**
 * dom操作
 */
export function hasClass(el, className) {
  let reg = new RegExp("(^|\\s)" + className + "(\\s|$)");
  return reg.test(el.className);
}
export function Trim(str, is_global = "") {
  var result;
  result = str.replace(/(^\s+)|(\s+$)/g, "");
  if (is_global.toLowerCase() == "g") {
    result = result.replace(/\s/g, "");
  }
  return result;
}
export function addClass(el, className) {
  if (hasClass(el, className)) {
    return;
  }

  let newClass = el.className.split(" ");
  newClass.push(className);
  el.className = newClass.join(" ");
}

export function removeClass(el, className) {
  if (!hasClass(el, className)) {
    return;
  }

  let reg = new RegExp("(^|\\s)" + className + "(\\s|$)", "g");
  el.className = el.className.replace(reg, " ");
}

export function attrFunc(el, name, val) {
  let prefix = "data-";
  if (val) {
    return el.setAttribute(prefix + name, val);
  }
  return el.getAttribute(prefix + name);
}

export function getRect(el) {
  if (el instanceof window.SVGElement) {
    let rect = el.getBoundingClientRect();
    return {
      top: rect.top,
      left: rect.left,
      width: rect.width,
      height: rect.height,
    };
  } else {
    return {
      top: el.offsetTop,
      left: el.offsetLeft,
      width: el.offsetWidth,
      height: el.offsetHeight,
    };
  }
}
export function offsetToBody(el) {
  let rect = el.getBoundingClientRect();

  return {
    left: -(rect.left + window.pageXOffset),
    top: -(rect.top + window.pageYOffset),
  };
}
export const qs = (selector, dom = document) => {
  return dom.querySelector(selector);
};
export const qsa = (selector, dom = document) => {
  return dom.querySelectorAll(selector);
};

export const ce = (tagName, attr = {}, dom = document) => {
  const element = dom.createElement(tagName);
  for (const p in attr) {
    element.setAttribute(p, attr[p]);
  }
  return element;
};
export const off = (el, evt, fn, opts = false) =>
  el.removeEventListener(evt, fn, opts);
// const fn = () => console.log('!');
// on(document.body, 'click', fn); // logs '!' upon clicking the body
// on(document.body, 'click', fn, { target: 'p' }); // logs '!' upon clicking a `p` element child of the body
// on(document.body, 'click', fn, { options: true });捕获不是冒泡 // use capturing instead of bubbling
export const on = (el, evt, fn, opts = {}) => {
  const delegatorFn = (e) =>
    e.target.matches(opts.target) && fn.call(e.target, e);
  el.addEventListener(
    evt,
    opts.target ? delegatorFn : fn,
    opts.options || false
  );
  if (opts.target) return delegatorFn;
};

export const insertAfter = (el, htmlString) =>
  el.insertAdjacentHTML("afterend", htmlString);
export const insertBefore = (el, htmlString) =>
  el.insertAdjacentHTML("beforebegin", htmlString);
// 浏览器选项卡是否可见
export const isBrowserTabFocused = () => !document.hidden;
//   elementContains(document.querySelector('head'), document.querySelector('title')); // true
// elementContains(document.querySelector('body'), document.querySelector('body')); // false
export const elementContains = (parent, child) =>
  parent !== child && parent.contains(child);
// 元素是否可见，第二参数为是否部分可见

export const getScrollPosition = (el = window) => ({
  x: el.pageXOffset !== undefined ? el.pageXOffset : el.scrollLeft,
  y: el.pageYOffset !== undefined ? el.pageYOffset : el.scrollTop,
});
/**
 * 存储localStorage
 */
export const setStore = (name, content) => {
  if (!name) return;
  if (typeof content !== "string") {
    content = JSON.stringify(content);
  }
  window.localStorage.setItem(name, content);
};

/**
 * 获取localStorage
 */
export const getStore = (name) => {
  if (!name) return;
  return window.localStorage.getItem(name);
};

/**
 * 删除localStorage
 */
export const removeStore = (name) => {
  if (!name) return;
  window.localStorage.removeItem(name);
};

/**
 * 获取style样式
 */
export const getStyle = (element, attr, NumberMode = "int") => {
  let target;
  // scrollTop 获取方式不同，没有它不属于style，而且只有document.body才能用
  if (attr === "scrollTop") {
    target = element.scrollTop;
  } else if (element.currentStyle) {
    target = element.currentStyle[attr];
  } else {
    target = document.defaultView.getComputedStyle(element, null)[attr];
  }
  //在获取 opactiy 时需要获取小数 parseFloat
  return attr === "scrollTop"
    ? NumberMode == "float"
      ? parseFloat(target)
      : parseInt(target)
    : target;
};

/**
 * 浏览器判断、操作
 */
export function isPc() {
  var ua = navigator.userAgent;
  if (
    ua.match(
      /(Android|webOS|iPhone|iPad|iPod|BlackBerry|Windows Phone|IEMobile|Opera Mini)/i
    )
  ) {
    console.log("手机环境");
    return false;
  } else {
    console.log("pc环境");
    return true;
  }
}
// 判断是不是苹果系统
export function isIos() {
  var ua = navigator.userAgent;
  // if ((ua.match(/(Android|webOS|Safari|iPhone|iPad|iPod|BlackBerry|Windows
  // Phone)/i))) {
  console.log(
    "%c%s",
    "color: white; background-color: #4facfe; padding: 2px 5px; border-radius: 2px",
    ua
  );
  if (/iPhone|iPad|iPod|Mac OS X/.test(ua)) {
    console.log(
      "%cios环境",
      "color: white; background-color: #f1738e; padding: 2px 5px; border-radius: 2px"
    );
    return true;
  } else {
    console.log(
      "%cAndroid | 谷歌",
      "color: white; background-color: #f1738e; padding: 2px 5px; border-radius: 2px"
    );
    return false;
  }
  // }
}
// 判断环境
export function Ua() {
  const inBrowser = typeof window !== "undefined";
  const inWeex =
    typeof WXEnvironment !== "undefined" && !!WXEnvironment.platform;
  const weexPlatform = inWeex && WXEnvironment.platform.toLowerCase();
  const UA = inBrowser && window.navigator.userAgent.toLowerCase();
  const isIE = UA && /msie|trident/.test(UA);
  const isIE9 = UA && UA.indexOf("msie 9.0") > 0;
  const isEdge = UA && UA.indexOf("edge/") > 0;
  const isAndroid =
    (UA && UA.indexOf("android") > 0) || weexPlatform === "android";
  const isIOS =
    (UA && /iphone|ipad|ipod|ios/.test(UA)) || weexPlatform === "ios";
  const isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge;
  const isPhantomJS = UA && /phantomjs/.test(UA);
  const isFF = UA && UA.match(/firefox\/(\d+)/);
  return {
    isIE,
    isIE9,
    isEdge,
    isIOS,
    isAndroid,
    isChrome,
    isPhantomJS,
    isFF,
  };
}
/**
 * @export
 * @param {*} hashKey
 * @param {*} hashName
 * @param {string} [type='push']
 * hashBack("id", "ipt", );
 */

export function hashBack(hashKey, hashName, type = "push") {
  if (type == "push") {
    window.history.pushState(
      {
        hashKey: hashName,
      },
      "",
      `#${hashName}`
    );
  } else {
    window.history.replaceState(
      {
        hashKey: hashName,
      },
      "",
      " "
    );
  }
}
// 获取url中的参数
export const getUrlParams = (
  url = decodeURIComponent(window.location.search)
) => {
  // 方法一
  /* const params = url.match(/([^?=&]+)(=([^&]*))/g);
  return params
    ? params.reduce(
        (a, v) => (
          (a[v.slice(0, v.indexOf("="))] = v.slice(v.indexOf("=") + 1)), a
        ),
        {}
      )
    : []; */
  // 方法二
  let q = {};
  url.replace(/([^?&=]+)=([^&]+)/g, (_, k, v) => (q[k] = v));
  return q;
  // 方法三   具有get has getAll(变数组返回) append toString 等方法
  // return new URLSearchParams(url);
};
/**
 * 数组操作
 *
 *
 */
// 返回数组索引 适用于唯一ID标识
export function findIndex(arr, dst) {
  var i = arr.length;
  while ((i -= 1)) {
    if (arr[i] == dst) {
      return i;
    }
  }
  return false;
}
// 求最大最小值
export function getMaxOrMin(numArray, type = "max") {
  if (type == "max") {
    return Math.max.apply(null, numArray);
  } else {
    return Math.min.apply(null, numArray);
  }
}
// 算百分数
function GetPercent(num, total) {
  num = parseFloat(num);
  total = parseFloat(total);
  if (isNaN(num) || isNaN(total)) {
    return "-";
  }
  return total <= 0 ? "0%" : Math.round((num / total) * 10000) / 100.0 + "%";
}
// 数组分割
// _.chunk(['a', 'b', 'c', 'd'], 2);
// => [['a', 'b'], ['c', 'd']]

// _.chunk(['a', 'b', 'c', 'd'], 3);
// => [['a', 'b', 'c'], ['d']]
function chunk(arr, size = 0) {
  let current = 0;
  let temp = [];
  return arr.reduce((acc, cur, i) => {
    if (current++ < size) {
      temp.push(cur);
    }
    if (temp.length === size || i === arr.length - 1) {
      acc.push(temp);
      current = 0;
      temp = [];
    }
    return acc;
  }, []);
}
// 抽象派
// function chunk(arr, size = 0) {
//   let current = 0;
//   let temp = [];
//   return arr.reduce(
//     (acc, cur, i) =>
//       temp.push(...(current++ < size ? [cur] : [])) === size ||
//       i === arr.length - 1
//         ? [...acc, temp, ...((current = 0), (temp = []), [])]
//         : acc,
//     []
//   );
// }
/*
 * JSON数组去重
 * @param: [array] json Array
 * @param: [string] 唯一的key名，根据此键名进行去重
 */
export function uniqueArray(array, key) {
  var result = [array[0]];
  for (var i = 1; i < array.length; i++) {
    var item = array[i];
    var repeat = false;
    for (var j = 0; j < result.length; j++) {
      if (item[key] == result[j][key]) {
        repeat = true;
        break;
      }
    }
    if (!repeat) {
      result.push(item);
    }
  }
  return result;
}

/**
 * 对象操作
 */
export function cloneObj(key) {
  if (key == null || key == undefined) {
    return;
  }
  return JSON.parse(localStorage.getItem(key));
}
/**
 * [clone 深度对象克隆]
 * @param  {[type]} Obj [参数对象]
 * 如果对象中带有函数 或者undefined 用此函数
 */
export function deepClone(Obj) {
  // 定义buf进行缓存
  var buf; // 创建一个空数组
  // if Array
  if (Obj instanceof Array) {
    // dosomething for Array Obj
    buf = [];
    var i = Obj.length;
    while (i--) {
      buf[i] = deepClone(Obj[i]);
    }
    return buf;
  }
  // if Object and Obj is not a function
  if (Obj instanceof Object && typeof Obj !== "function") {
    // dosomething for Object Obj
    buf = {}; // 创建一个空对象
    for (var key in Obj) {
      buf[key] = deepClone(Obj[key]);
    }
    return buf;
  }
  // else
  else {
    // dosomething for other
    return Obj;
  }
}

// 判断对象是否为空
export const flag = (obj) =>
  !Array.isArray(obj) &&
  Object.prototype.toString.call(obj) &&
  !Object.keys(obj).length;
// 生成数组 ArrayFill(5, 2); // [2, 2, 2, 2, 2]
export const ArrayFill = (n, val = 0) => Array(n).fill(val);
// dropRight([1, 2, 3]); // [1,2]
// dropRight([1, 2, 3], 2); // [1]
// dropRight([1, 2, 3], 42); // []
export const dropRight = (arr, n = 1) => arr.slice(0, -n);
// dropWhile([1, 2, 3, 4], n => n >= 3); // [3,4]
export const dropWhile = (arr, func) => {
  while (arr.length > 0 && !func(arr[0])) arr = arr.slice(1);
  return arr;
};

/**
 * 界面功能性操作
 */
// 判断当前位置是否为页面底部
export function bottomVisible() {
  return (
    document.documentElement.clientHeight + window.scrollY >=
    (document.documentElement.scrollHeight ||
      document.documentElement.clientHeight)
  );
}
// 判断元素是否在可视范围内
// partiallyVisible为是否为完全可见
export const elementIsVisibleInViewport = (el, partiallyVisible = false) => {
  const { top, left, bottom, right } = el.getBoundingClientRect();
  let innerHeight = window.innerHeight;
  let innerWidth = window.innerWidth;
  return partiallyVisible
    ? ((top > 0 && top < innerHeight) ||
        (bottom > 0 && bottom < innerHeight)) &&
        ((left > 0 && left < innerWidth) || (right > 0 && right < innerWidth))
    : top >= 0 && left >= 0 && bottom <= innerHeight && right <= innerWidth;
};
/**
 * underscore 节流函数，返回函数连续调用时，func 执行频率限定为 次 / wait
 *
 * @param  {function}   func      回调函数
 * @param  {number}     wait      表示时间窗口的间隔
 * @param  {object}     options   如果想忽略开始函数的的调用，传入{leading: false}。
 *                                如果想忽略结尾函数的调用，传入{trailing: false}
 *                                两者不能共存，否则函数不能执行
 * @return {function}             返回客户调用函数
 */
export function throttle(func, wait, options) {
  var context, args, result;
  var timeout = null;
  // 之前的时间戳
  var previous = 0;
  // 如果 options 没传则设为空对象
  if (!options) options = {};

  // 定时器回调函数
  var later = function () {
    // 如果设置了 leading，就将 previous 设为 0 用于下面函数的第一个 if 判断
    previous = options.leading === false ? 0 : +new Date();
    // 置空一是为了防止内存泄漏，二是为了下面的定时器判断
    timeout = null;
    result = func.apply(context, args);
    if (!timeout) context = args = null;
  };
  return function () {
    // 获得当前时间戳
    var now = +new Date();
    // 首次进入前者肯定为 true 如果需要第一次不执行函数 就将上次时间戳设为当前的 这样在接下来计算 remaining 的值时会大于0
    if (!previous && options.leading === false) previous = now;

    // 计算剩余时间
    var remaining = wait - (now - previous);
    context = this;
    args = arguments;
    // 如果当前调用已经大于上次调用时间 + wait 或者用户手动调了时间 如果设置了 trailing，只会进入这个条件 如果没有设置
    // leading，那么第一次会进入这个条件 还有一点，你可能会觉得开启了定时器那么应该不会进入这个 if 条件了 其实还是会进入的，因为定时器的延时
    // 并不是准确的时间，很可能你设置了2秒 但是他需要2.2秒才触发，这时候就会进入这个条件
    if (remaining <= 0 || remaining > wait) {
      // 如果存在定时器就清理掉否则会调用二次回调
      if (timeout) {
        clearTimeout(timeout);
        timeout = null;
      }
      previous = now;
      result = func.apply(context, args);
      if (!timeout) context = args = null;
    } else if (!timeout && options.trailing !== false) {
      // 判断是否设置了定时器和 trailing 没有的话就开启一个定时器 并且不能不能同时设置 leading 和 trailing
      timeout = setTimeout(later, remaining);
    }
    return result;
  };
}
/**
 * underscore 防抖函数，返回函数连续调用时，空闲时间必须大于或等于 wait，func 才会执行
 * 滚动事件，如果一直滚动 事件不执行，停止后，wait等待才执行
 *
 * @param  {function} func        回调函数
 * @param  {number}   wait        表示时间窗口的间隔
 * @param  {boolean}  immediate   设置为ture时，是否立即调用函数
 * @return {function}             返回客户调用函数
 */
export function debounce(func, wait, immediate) {
  var timeout, args, context, timestamp, result;
  var later = function () {
    // 现在和上一次时间戳比较
    var last = +new Date() - timestamp;
    // 如果当前间隔时间少于设定时间且大于0就重新设置定时器
    if (last < wait && last >= 0) {
      timeout = setTimeout(later, wait - last);
    } else {
      // 否则的话就是时间到了执行回调函数
      timeout = null;
      if (!immediate) {
        result = func.apply(context, args);
        if (!timeout) context = args = null;
      }
    }
  };
  return function () {
    context = this;
    args = arguments;
    // 获得时间戳
    timestamp = +new Date();
    // 如果定时器不存在且立即执行函数
    var callNow = immediate && !timeout;
    // 如果定时器不存在就创建一个
    if (!timeout) timeout = setTimeout(later, wait);
    if (callNow) {
      // 如果需要立即执行函数的话 通过 apply 执行
      result = func.apply(context, args);
      context = args = null;
    }
    return result;
  };
}

// 数字千分位分割
export function commafy(num) {
  return num.toString().indexOf(".") !== -1
    ? num.toLocaleString()
    : num.toString().replace(/(\d)(?=(?:\d{3})+$)/g, "$1,");
}
// 生成随机数
export function randomNum(min, max) {
  switch (arguments.length) {
    case 1:
      return parseInt(Math.random() * min + 1, 10);
    case 2:
      return parseInt(Math.random() * (max - min + 1) + min, 10);
    default:
      return 0;
  }
}

export const type = (obj) => {
  let class2type = {};
  "Boolean Number String Function Array Date RegExp Object Error Symbol"
    .split(" ")
    .map((d, i) => {
      return (class2type["[object " + d + "]"] = d.toLowerCase());
    });
  if (obj == null) {
    return obj + "";
  }
  return typeof obj === "object" || typeof obj === "function"
    ? class2type[Object.prototype.toString.call(obj)] || "object"
    : typeof obj;
};

// 平滑移动
// smoothScroll('#fooBar');
export const smoothScroll = (element) =>
  document.querySelector(element).scrollIntoView({
    behavior: "smooth",
  });
// 返回顶部
export const scrollToTop = () => {
  const c = document.documentElement.scrollTop || document.body.scrollTop;
  if (c > 0) {
    window.requestAnimationFrame(scrollToTop);
    window.scrollTo(0, c - c / 8);
  }
};
// 验证码倒计时

/**
 *
 *
 * @param {*} num 倒计时时间
 * @param {*} perCallback   (num) => { this.sendText = `${num}秒`;},
 * @param {*} endCallback   (num) => { this.sendText = `发送验证码`;},
 */
export const countDownTime = (num, perCallback, endCallback) => {
  perCallback(num);
  setTimeout(() => {
    if (num <= 0) {
      endCallback();
    } else {
      countDownTime(num - 1, perCallback, endCallback);
    }
  }, 1000);
};

/**
 * 获取图片的 base64 编码
 * @param image 图像对象
 * @return {string} 返回已编码的 base64数据
 */
export const getImageBase64 = (image) => {
  const canvas = document.createElement("canvas");
  canvas.width = image.width;
  canvas.height = image.height;
  const ctx = canvas.getContext("2d");
  ctx.drawImage(image, 0, 0, image.width, image.height);
  // 获取图片后缀名
  const extension = image.src
    .substring(image.src.lastIndexOf(".") + 1)
    .toLowerCase();
  // 某些图片 url 可能没有后缀名，默认是 png
  return canvas.toDataURL("image/" + extension, 1);
};

/**
 * 单张图片下载
 * @param url 图像链接
 * @param downloadName 图片名称
 */
export const downloadImage = (url, downloadName = +new Date()) => {
  const link = document.createElement("a");
  link.setAttribute("download", downloadName);
  const image = new Image();
  // 是否需要拼接 url
  if (url.indexOf("blob:") == -1) {
    // 添加时间戳，防止浏览器缓存图片
    url += url.indexOf("?") == -1 ? "?" : "&"; //判断是否有问号
    url += "timestamp=" + new Date().getTime();
  }
  image.src = url;
  // 设置 crossOrigin 属性，解决图片跨域报错
  image.setAttribute("crossOrigin", "Anonymous");
  image.onload = () => {
    link.href = getImageBase64(image);
    link.click();
  };
};
// arraybuffer bolb等 手动下载方法
export const downloadFile = (
  data,
  fileName = +new Date(),
  isRemoveBolb = false
) => {
  let url;
  if (data != String) {
    url = window.URL.createObjectURL(
      new Blob([data], {
        // 根据文件类型添加
        type: "application/octet-stream",
      })
    );
    isRemoveBolb = true;
  } else {
    url = data;
    isRemoveBolb = false;
  }
  let link = document.createElement("a");
  link.style.display = "none";
  link.href = url;
  link.setAttribute("download", fileName);
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
  isRemoveBolb && window.URL.revokeObjectURL(url); // 释放掉blob对象
};
// url下载视频
export const downloadVideo = (url, name) => {
  var videoUrl = url;
  var fileName = name || `${+new Date()}.mp4`; // 下载的文件名
  var a = document.createElement("a");
  a.style.display = "none";
  document.body.appendChild(a);
  var xhr = new XMLHttpRequest();
  xhr.open("GET", videoUrl, true);
  xhr.responseType = "blob";
  xhr.onload = function () {
    if (xhr.status === 200) {
      var videoBlob = xhr.response;
      var url = window.URL.createObjectURL(videoBlob);
      a.href = url;
      a.download = fileName;
      a.click();
      window.URL.revokeObjectURL(url);
    }
  };
  xhr.send();
};
/**
 * 下载文件blob流  要求responseType 返回为blob
 * @param response
 * @returns
 */
export const downloadBlobFile = (response, fileName) => {
  return new Promise((resolve, reject) => {
    const fileReader = new FileReader();
    fileReader.onload = function () {
      try {
        // console.log("result:", this.result);
        const jsonData = JSON.parse(result); // 成功 说明是普通对象数据
        if (jsonData?.code !== 200) {
          // Message.error(jsonData?.message ?? "请求失败");
          console.log(jsonData?.message ?? "请求失败");
          reject(jsonData);
        }
      } catch (err) {
        // 解析成对象失败，说明是正常的文件流
        const blob = new Blob([response.data]);
        // 本地保存文件
        const url = window.URL.createObjectURL(blob);
        const link = document.createElement("a");
        link.href = url;
        const filename =
          response?.headers?.["content-disposition"] || fileName || +new Date();
        // const filename = response?.headers?.["content-disposition"]
        //   ?.split("filename*=")?.[1]
        //   ?.substr(7);
        link.setAttribute("download", decodeURI(filename));
        document.body.appendChild(link);
        link.click();
        resolve(response.data);
      }
    };
    fileReader.readAsText(response.data);
  });
};
// 对象转formdata

/**
 *
 *
 * @export
 * @param {*} obj
 * @param {*} isUploadFile  file是json数组的时候  抽出raw 到formdata
 * @return {*}
 */
export function paramsToFormData(obj, isUploadFile = false) {
  const formData = new FormData();
  Object.keys(obj).forEach((key) => {
    if (obj[key] instanceof Array) {
      if (isUploadFile) {
        for (let i = 0; i < obj[key].length; i++) {
          formData.append(`file[${i}]`, obj[key][i].file);
        }
      } else {
        obj[key].forEach((item) => {
          formData.append(key, item);
        });
      }
      return;
    }
    formData.append(key, obj[key]);
  });
  return formData;
}

// 验证是否为blob格式
export async function blobValidate(data) {
  try {
    const text = await data.text();
    JSON.parse(text);
    return false;
  } catch (error) {
    return true;
  }
}
// file转 base 64
export function uploadImgToBase64(file) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = function () {
      // 图片转base64完成后返回reader对象
      resolve(reader);
    };
    reader.onerror = reject;
  });
}
